/*****************************************************************************
Copyright 2006 Kervin L. Pierre
    EMAIL: otlkcon@adevsoft.com

This file is part of Otlkcon.

Otlkcon is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

Otlkcon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Otlkcon; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/

#include "StdAfx.h"

#ifdef OTLKCON_AB

#include <mspst.h>

#include "O_IMSProvider.h"
#include "O_IABProvider.h"
#include "mstore.h"

extern vector<O_IMSProvider *> otlkcon_store_providers;
extern CRITICAL_SECTION otlkcon_store_providers_cs;

extern const IID IID_IABProvider;

O_IABProvider::O_IABProvider()
{
    InitializeCriticalSection(&objectCS);
}

HRESULT O_IABProvider::Create(otlkcon_memory *m)
{
    HRESULT result = S_OK;

    memObj = m;
    cRef = 1;
    abpEnabled = FALSE;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
        L"O_IABProvider::Create() called.\n");

    return result;
}

HRESULT O_IABProvider::Destroy()
{
    HRESULT result = S_OK;

    return result;
}

STDMETHODIMP O_IABProvider::QueryInterface (REFIID riid, void **ppv)
{
    HRESULT result = S_OK;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
        L"inside O_IABProvider->QueryInterface. \
 {%0.8X-%0.4X-%0.4X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X}\n",
        riid.Data1, riid.Data2, riid.Data3, riid.Data4[0], riid.Data4[1],
        riid.Data4[2], riid.Data4[3], riid.Data4[4], riid.Data4[5],
        riid.Data4[6], riid.Data4[7]);

    *ppv = NULL;

    if( memcmp(&riid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(&riid, &IID_IABProvider, sizeof(IID)))
    { 
        result = ResultFromScode(E_NOINTERFACE);
        return result;
    }

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
        L"\t{%0.8X-%0.4X-%0.4X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X} found\n",
        riid.Data1, riid.Data2, riid.Data3, riid.Data4[0], riid.Data4[1],
        riid.Data4[2], riid.Data4[3], riid.Data4[4], riid.Data4[5],
        riid.Data4[6], riid.Data4[7]);

    AddRef();
    *ppv = (LPVOID)this;

    return result;
}

ULONG STDMETHODCALLTYPE O_IABProvider::AddRef (void)
{
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"inside O_IABProvider->AddRef()\n" );

    return ++cRef;
}

ULONG STDMETHODCALLTYPE O_IABProvider::Release (void)
{
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"inside O_IABProvider->Release()\n" );

    if ( --cRef == 0 )
    {
        Destroy();
    }

    return cRef;
}

HRESULT O_IABProvider::ProcessEvent( otlkcon_event_struct *ev )
{
    HRESULT result = S_OK;

    switch( ev->eventType )
    {
        case OTLKCON_EVENT_RELOAD_CONFIG:
        default:
            OTLKCON_DEBUGBREAK;
            break;
    }

    return result;
}

 HRESULT O_IABProvider::Logon( LPMAPISUP lpMAPISup,
     ULONG ulUIParam,
     LPTSTR lpszProfileName,
     ULONG ulFlags,
     ULONG *lpulcbSecurity,
     LPBYTE *lppbSecurity,
     LPMAPIERROR *lppMAPIError,
     LPABLOGON *lppABLogon )
 {
    HRESULT result = S_OK;
    otlkcon_generic_msgstore *tempStore = NULL;
    O_IABLogon *tempAB = NULL;

    if( ulFlags & AB_NO_DIALOG )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"IABProvider::Logon() : AB_NO_DIALOG set.\n" );
    }
    if( ulFlags & MAPI_DEFERRED_ERRORS )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"IABProvider::Logon() : MAPI_DEFERRED_ERRORS set.\n" );
    }
    if( ulFlags & MAPI_UNICODE )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"IABProvider::Logon() : MAPI_UNICODE set.\n" );
    }

    *lpulcbSecurity = 0;
    *lppbSecurity   = NULL;
    *lppMAPIError   = NULL;

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_IABProvider::Logon");

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"inside O_IABProvider->Logon()\n" );

    result = lpMAPISup->AddRef();

    // Create or Load...
    O_IABPMsgStore *msgstore = NULL;
	result = GetStore(lpMAPISup, ulUIParam, &msgstore, ulFlags);
    if( result != S_OK )
    {
        goto logon_end;
    }

    // FIXME: We need to find a good way to disable ABP.
    //        Apparently E_USER_CANCEL causes Outlook to
    //        exit.

    //  if( abpEnabled == FALSE )
    //  {
    //      // We are not allowed to run for now

	//*lppABLogon = NULL;
	//return MAPI_E_USER_CANCEL;
    //  }

    tempAB = new O_IABLogon();

    // There is a 1-to-1 relationship between the store and logon
    // object.
    result = tempAB->Create( msgstore );
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    *lppABLogon = tempAB;

    // This registers for the address book provider not the
    // message store provider.
    result = lpMAPISup->SetProviderUID( 
                &(msgstore->entryID->storeInstanceGuid), 0);

	// Add a row to the status table for the address book provider
    // This message store does not get a status row, if it does
    // not need one
    SPropValue tempProps[8] = {0};

	/* TODO: Put distinguishing information, eg. a file path in DISPLAY_NAME */
	/* TODO: This should be conditional */
	tempProps[0].ulPropTag = PR_DISPLAY_NAME;
	tempProps[0].Value.lpszA = OTLKCON_STORE_DISPLAY_NAME_A;

	//FIXME: This should be done in otlkcon_generic_msgstore::Create()
	msgstore->_IntSetProps(1, tempProps, NULL);

	tempProps[1].ulPropTag = PR_PROVIDER_DISPLAY;
	tempProps[1].Value.lpszA = "OpenConnector";

	tempProps[2].ulPropTag = PR_STATUS_CODE;
	tempProps[2].Value.l = STATUS_AVAILABLE;

	tempProps[3].ulPropTag = PR_RESOURCE_METHODS;
	tempProps[3].Value.l = STATUS_SETTINGS_DIALOG;

	tempProps[4].ulPropTag = PR_STATUS_STRING;
	tempProps[4].Value.lpszA = "Available";

	tempProps[5].ulPropTag = PR_IDENTITY_DISPLAY;
	tempProps[5].Value.lpszA = "My Username";
	
	tempProps[6].ulPropTag = PR_IDENTITY_ENTRYID;
	tempProps[6].Value.bin.cb = sizeof(O_ENTRYID);
    tempProps[6].Value.bin.lpb = (LPBYTE)&(msgstore->entryID);
	
	tempProps[7].ulPropTag = PR_IDENTITY_SEARCH_KEY;
    tempProps[7].Value.bin.cb = 14;
    tempProps[7].Value.bin.lpb = (LPBYTE)"My Search Key";

	result = lpMAPISup->ModifyStatusRow( 8, tempProps, 0 );
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

logon_end:
    return result;
 }

 // Create or Load a message store if none is available.
 //
 // In the future this function maybe called once per
 // backend.
 //
 // The plan is to not register this 'private' message
 // store object.
 HRESULT O_IABProvider::GetStore(LPMAPISUP lpMAPISup, ULONG ulUIParam,
     O_IABPMsgStore **msgstore, ULONG ulFlags)
 {
	HRESULT result = S_OK;
	LPPROFSECT lpProfileObj = NULL;
	ULONG profPropsCount = 0, res=0;
	MAPIUID instanceGuid = {0};  
    LPSPropValue profProps;

    // Open the service profile can return the necessary
    // properties if available.

    // Before anything, lets get some information from the profile
    // Get the profile properties
    result = lpMAPISup->OpenProfileSection(NULL, MAPI_MODIFY, &lpProfileObj);
    if( result != S_OK )
    {
        // call failed
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"IABProvider::GetStore() : OpenProfileSection failed.\n" );
        result = MAPI_E_UNCONFIGURED;
        goto getstore_end;
    }

    SizedSPropTagArray(50, profTags);

    profPropsCount = 0;
    profTags.aulPropTag[profPropsCount++] = PR_SERVICE_UID;
    profTags.cValues = profPropsCount;

    O_ENTRYID *profEID = NULL;
    char      *profProviderDisplay = NULL, *dbFilename = NULL;
    MAPIUID   *profServiceUid = NULL, *profRecordKey = NULL, *profAbProvider = NULL;
    LPPROFSECT lpServiceProfileObj = NULL;
    wchar_t strBuff[MAX_PATH+1] = {0};

    result = lpProfileObj->GetProps((LPSPropTagArray)&profTags, 0, &profPropsCount, &profProps);
    switch( result )
	{
		case MAPI_W_ERRORS_RETURNED:
		case S_OK:			
            for( ULONG i=0; i<profPropsCount; i++ )
            {
                if( PROP_ID(profProps[i].ulPropTag) == PROP_ID(PR_SERVICE_UID) )
				{
					if( PROP_TYPE(profProps[i].ulPropTag) == PT_BINARY )
					{
						profServiceUid = (MAPIUID*)(profProps[i].Value.bin.lpb);
					}
                }
            }
    }

    result = lpMAPISup->OpenProfileSection(profServiceUid, MAPI_MODIFY,
                                &lpServiceProfileObj);
    if(SUCCEEDED(result))
    {
        profPropsCount = 0;
        profTags.aulPropTag[profPropsCount++] = PR_PST_PATH;
        profTags.aulPropTag[profPropsCount++] = PR_OTLKCON_AB_ENABLED;
	    profTags.cValues = profPropsCount;

        result = lpServiceProfileObj->GetProps((LPSPropTagArray)&profTags, 0, &profPropsCount, &profProps);
        switch( result )
        {
            case MAPI_W_ERRORS_RETURNED:
            case S_OK:			
            for( ULONG i=0; i<profPropsCount; i++ )
            {
                if( PROP_ID(profProps[i].ulPropTag) 
                                            == PROP_ID(PR_PST_PATH) )
	            {
		            if( PROP_TYPE(profProps[i].ulPropTag) == PT_STRING8 )
		            {
                        dbFilename = profProps[i].Value.lpszA;
		            }
	            }
                else if( PROP_ID(profProps[i].ulPropTag) 
                                            == PROP_ID(PR_OTLKCON_AB_ENABLED) )
	            {
		            if( PROP_TYPE(profProps[i].ulPropTag) == PT_BOOLEAN )
		            {
                        abpEnabled = profProps[i].Value.b;
		            }
	            }
            }
        }
    }

    int dbFilenameValid = 0;
    result = otlkcon_testDbFilename( dbFilename, 
                                &dbFilenameValid );

    if( dbFilenameValid == 0 )
    {
        // db file not found
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"IMAPIProp database file not found.\n" );
        result = MAPI_E_UNCONFIGURED;
        goto getstore_end;
    }

    // If any of the following properties exist, we should use those
    // instead of generating new values??  If they do not exist, then
    // generate new values??

    profPropsCount = 0;
    profTags.aulPropTag[profPropsCount++] = PR_OTLKCON_ENTRYID;
    profTags.aulPropTag[profPropsCount++] = PR_PROVIDER_DISPLAY;
    profTags.aulPropTag[profPropsCount++] = PR_AB_PROVIDER_ID;
    profTags.aulPropTag[profPropsCount++] = PR_RECORD_KEY;
    profTags.aulPropTag[profPropsCount++] = PR_SERVICE_UID;
    profTags.aulPropTag[profPropsCount++] = PR_PROVIDER_UID;
    profTags.aulPropTag[profPropsCount++] = PR_PST_PATH;
	profTags.cValues = profPropsCount;

    result = lpProfileObj->GetProps((LPSPropTagArray)&profTags, 0, &profPropsCount, &profProps);
    switch( result )
	{
		case MAPI_W_ERRORS_RETURNED:
		case S_OK:			
            for( ULONG i=0; i<profPropsCount; i++ )
            {
                if( PROP_ID(profProps[i].ulPropTag) == PROP_ID(PR_OTLKCON_ENTRYID) )
                {
                    if( PROP_TYPE(profProps[i].ulPropTag) == PT_BINARY )
                    {
                        profEID = (O_ENTRYID*)(profProps[i].Value.bin.lpb);
                    }    
                }
                else if( PROP_ID(profProps[i].ulPropTag) == PROP_ID(PR_PROVIDER_DISPLAY) )
                {
                    if( PROP_TYPE(profProps[i].ulPropTag) == PT_STRING8 )
                    {
                        profProviderDisplay = _strdup(profProps[i].Value.lpszA);
                    }
                }
                else if( PROP_ID(profProps[i].ulPropTag) == PROP_ID(PR_AB_PROVIDER_ID) )
                {
                    // FIXME: Don't think that this property is being set
                    //        currently.  It needs to correspond with the
                    //        entryID of the addressbook.
                    if( PROP_TYPE(profProps[i].ulPropTag) == PT_BINARY )
                    {
                       profAbProvider =  (MAPIUID*)(profProps[i].Value.bin.lpb);
                       if( memcmp(profAbProvider, &otlkcon_mdb_provider_guid, sizeof(MAPIUID)) )
                       {
                           OTLKCON_DEBUGBREAK;
                       }
                    }
                }
                else if( PROP_ID(profProps[i].ulPropTag) == PROP_ID(PR_RECORD_KEY) )
                {
                    if( PROP_TYPE(profProps[i].ulPropTag) == PT_BINARY )
                    {
                        profRecordKey = (LPMAPIUID)(profProps[i].Value.bin.lpb);
                    }
                }
                else if( PROP_ID(profProps[i].ulPropTag) == PROP_ID(PR_SERVICE_UID) )
                {
                    if( PROP_TYPE(profProps[i].ulPropTag) == PT_BINARY )
                    {
                        profServiceUid = (MAPIUID*)(profProps[i].Value.bin.lpb);
                    }
                }
            }
			break;

		default:
			//Error condition
            // Call failed 
            OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
                L"GetProfileProperties: GetProps failed.\n" );
            result = MAPI_E_UNCONFIGURED;
            goto getstore_end;
    }

#ifdef OTLKCON_DELETE_MSTORE_DB
    // For debugging, start with a fresh file
    // FIXME: Set a global variable so this is done
    //        only once per load.
    DeleteFile(dbFilename);
    Sleep( OTLKCON_DELETE_PAUSE );
#endif // OTLKCON_DELETE_MSTORE_DB

	memset(&instanceGuid, 0, sizeof(MAPIUID));

    if( profRecordKey )
    {
        //We got a record key from the profile, may as well use it.
        memcpy(&instanceGuid, profRecordKey, sizeof(MAPIUID));
    }

	// create MsgStore object
    (*msgstore) = new O_IABPMsgStore();
	//Create() needs the support object
	(*msgstore)->lpMAPISup = lpMAPISup;
    (*msgstore)->ulUIParam = ulUIParam;
    (*msgstore)->logonFlags = ulFlags;

	if( profEID )
    {
        // FIXME:  Is this EntryID wrapped?

        // We have already check in memory for this object above        
		result = (*msgstore)->Load(dbFilename, profEID, memObj);
        if( result == MAPI_E_CORRUPT_DATA )
        {
            // TODO:  The database file or store object is corrupt.
            //        Do we need a repair mode or something?
            OTLKCON_DEBUGBREAK;
        }
		else if( result == MAPI_E_NOT_FOUND )
		{
            //We can try to create if the message store did not
            //exist as yet.

			O_ENTRYID* lpOurEntryID	= (O_ENTRYID*)profEID;
			
			result = (*msgstore)->Create(dbFilename, lpOurEntryID->storeInstanceGuid, memObj);
            if( result == MAPI_E_COLLISION )
            {
                // This object exists, but didn't load.
                // Not good....
                OTLKCON_DEBUGBREAK;
            }
		} // if

        if( profRecordKey )
        {
            // FIXME:  We have a record key from the profile
            //         Most likely we will have to change the profile
            //         since all existing objects of this store has
            //         its record key already.
            //OTLKCON_DEBUGBREAK;
        }
    }
    else
    {
        result = (*msgstore)->Create(dbFilename, instanceGuid, memObj);
        if( result != S_OK )
        {
            OTLKCON_DEBUGBREAK
            goto getstore_end;
        }
    }

	// Set any profiles properties needed.
	SPropValue tempProps[8] = {0};
    //update profile if we need that
	if(NULL == profEID)
    {

		//FIXME: This may cause conflict between multiple msgstore instances
		tempProps[0].ulPropTag = PR_RECORD_KEY;
		tempProps[0].Value.bin.lpb 
            = (LPBYTE)(profRecordKey?profRecordKey:&((*msgstore)->entryID->storeInstanceGuid));
        tempProps[0].Value.bin.cb  = sizeof(MAPIUID);

		tempProps[1].ulPropTag = PR_PST_PATH;
		tempProps[1].Value.lpszA = dbFilename;

		//We need to set entryID
		tempProps[2].ulPropTag = PR_OTLKCON_ENTRYID;
        tempProps[2].Value.bin.lpb = (LPBYTE)((*msgstore)->entryID);
        tempProps[2].Value.bin.cb  = sizeof(O_ENTRYID);

		result = lpProfileObj->SetProps(3, tempProps, NULL);
		
    } // if
	if(NULL != lpProfileObj)
	{
		lpProfileObj->Release();
		lpProfileObj = NULL;
	} // if
    
	if( FAILED(result) )
    {
        OTLKCON_DEBUGBREAK
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"ERROR: result == %x", result );
        
        // TODO: cleanup, build error struct 
        result = MAPI_E_FAILONEPROVIDER;
        goto getstore_end;
    }

  //  if(profRecordKey)
  //  {
		//lpMAPISup->ModifyProfile(ulFlags & MDB_TEMPORARY);
  //  } // if

getstore_end:
	return result;
 }

 HRESULT O_IABProvider::Shutdown(  ULONG *lpulFlags  )
 {
    HRESULT result = S_OK;
 
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"inside O_IABProvider->Shutdown()\n" );

    return result;
 }

#endif // OTLKCON_AB